package com.jsecode.protocol.gid22;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jsecode.lib.util.ByteUtil;
import com.jsecode.net.common.NetUtil;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;

/**
 * 数据的包信息
 *    FAFA         2bytes
 *    seq          1byte
 *    msgtype      1byte
 *    terminalid   6bytes
 *    bodylen      2bytes
 *    body
 *    XOR 1byte   对前面的所有数据去异或, responseCS = (byte)((byte)responseCS ^ data[i]);
 *    FB  1byte
 *
 *
 * @author grind
 * @date Mar 28, 2014 3:03:57 PM
 * @version 1.0
 */
public class GidPack {
    protected static Logger logger = LoggerFactory.getLogger("gid22-GidPack");
    
    public final static int MARK_START_FLAG     = 0xFAFA;
    public final static byte MARK_END_FLAG     = (byte)0xFB;
    public final static int EXT_LEN = 4;
    
    // public int mark_start; // 0xFAFA
    protected GidHeader header;
    protected IGidBody body;
    public byte crc;  // 对前面的所有数据去异或, responseCS = (byte)((byte)responseCS ^ data[i]);
    // public byte mark_end;

    // 协议外数据
    private Channel channel; // 本连接对象接受到的数据
    private int errCode; // 0 - 正常
    
    public GidPack(){
    }

    public GidPack(GidHeader header, IGidBody body){
        this.header = header;
        this.body = body;
    }

    public void setHeader(GidHeader header){
        this.header = header;
    }
    
    public GidHeader getHeader(){
        return header;
    }
    
    public void setBody(IGidBody body){
        this.body = body;
    }
    
    public IGidBody getBody(){
        return body;
    }
    
    public void setChannel(Channel channel){
        this.channel = channel;
    }
    
    public Channel getChannel(){
        return channel;
    }
    
    public void setErrCode(int errCode){
        this.errCode = errCode;
    }
    
    public int getErrCode(){
        return errCode;
    }
    
    @Override
    public String toString(){
        //return ToStringBuilder.reflectionToString(this);
        StringBuilder sb = new StringBuilder();
        sb.append("header=[").append(header).append("],");
        sb.append("body=[").append(body).append("]");
        
        return sb.toString();
    }
    
    /** 
     * 发送消息时打包的流程:消息封装——>计算并填充校验码——>头尾尾标识;
     * @return 打包后的buffer(可以通过netty直接发送，netty会计算需要的长度)
     */
    public ByteBuf pack() {
        try {
            // 消息封装
            ByteBuf bodyBuf  = null;
            if (body != null) { 
                bodyBuf = body.pack();
            }
    
            int capicity = 0;
            if (bodyBuf != null) {
                capicity = bodyBuf.readableBytes();
            }
            header.setBodyLen(capicity);
            ByteBuf headerBuf = header.pack();
            capicity += headerBuf.readableBytes() + EXT_LEN;
    
            ByteBuf buf = Unpooled.buffer(capicity);
            buf.writeShort(MARK_START_FLAG);
            buf.writeBytes(headerBuf);
            if (bodyBuf != null) {
                buf.writeBytes(bodyBuf);
            }
            // 计算并填充校验码
            crc = calcSum(buf.array(), 0, buf.readableBytes());
            buf.writeByte(crc);
            buf.writeByte(MARK_END_FLAG);
            
            return buf;
        } catch (Exception e) {
            logger.error("pack error with: "  + e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 接收消息时:验证校验码——>解析消息。
     * 输入的数据是一个完整数据包
     * @return -1 crc校验错误， 0 成功  -2 车机未注册
     */
    public int unpack(ByteBuf srcBuf) {
        errCode = -1;
        try {
            // 验证校验码
            boolean isCrcRight = checkSum(srcBuf.array(), 0, srcBuf.readableBytes() - 1);
            if (!isCrcRight) {
                logger.warn("CRC 校验错误");
                return -1;
            }
            
            srcBuf.writerIndex(srcBuf.writerIndex() - 2); // remove the crc data and endMark:0xFB
            srcBuf.readShort(); // skip header 0xFAFA
    
            errCode = unpackNoCrc(srcBuf);
        } catch (Exception e) {
            logger.error("unpack with error:", e);
        } finally {
            // 交给用户处理
            afterDecoded(this);
        }
        return errCode;
    }
    
    /**
     * 输入为已经转义还原的数据, 验证校验码并解析头消息, 和数据体。
     * @return -1 crc校验错误， 0 成功
     */
    public int unpack() {
        logger.warn("TODO: unpack()");
        return -1;
    }

    /**
     * 接收消息时: 解析消息。
     * 输入的数据是一个完整数据包
     * @return -1 fail 0 成功  -2 车机未注册
     */
    public int unpackNoCrc(ByteBuf srcBuf) {
        int retVal = 0;
        // 解析头
        header = new GidHeader();
        if (0 == header.unpack(srcBuf)) {
            // 检查是否允许当前车机接入, 如果服务器未注册，断开当前连接，停止解析
            int result = checkHeader(header);
            if (result < 0) {
                logger.warn("Error when checkHeader, deviceId = " + header.getDeviceId());
                return -2;
            }

            // 解析数据体
            retVal = unpackBody(srcBuf);
            logger.info("MsgPack:" + this);
        } else {
            logger.warn("unpack head with error");
        }

        return retVal;
    }
    
    /**
     * 解析完成头包，以后对头部的检查，包括是否允许继续解析等
     * 子类可以通过重载， 来进行对应的业务操作
     * 
     * @param header
     * @return 0 正常, 继续解析; < 0 错误
     */
    public int checkHeader(GidHeader header) {
        return 0;
    }
    
    /**
     * 继承类通过重写这个方法来实现定制化的功能
     * 
     * @param pack
     */
    public void afterDecoded(GidPack pack) {
        // please implement it in subclass
    }
    
    /**
     * 根据信息头解析数据体的消息
     * @param srcBuf readIndex为数据体的开始字节
     * @return
     */
    public int unpackBody(ByteBuf srcBuf) {
        if (header == null) {
            return -1;
        }
        int retVal = 0;
        ByteBuf sendBuf = null;
        logger.info("MSGID: 0x{},  TERMINALID: {}",Integer.toHexString(header.getMsgId()),  header.getDevId());
        switch (header.getMsgId()) {
            case GidMsgID.GPS_REPORT_FLAG:
                retVal = gpsReport(srcBuf, false);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.CAN_FLAG:
                retVal = canReport(srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.DTC_FLAG:
                // 不去解析该数据
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
                /***
            case GidMsgID.DV_FLAG:
                retVal = versionReport(srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.ALERT_FLAG:
                retVal = gpsReport(srcBuf, true);
                sendBuf = GidPFWarnRsp.packRsp(header, ((GidGps)body).getWarn1(), ((GidGps)body).getWarn2(), ((GidGps)body).getWarn3());
                // 平台确认报警应答 
                break;
            case GidMsgID.HEARTBEAT_FLAG:
                Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.DRIVER_ACTION_FLAG:
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.SIM_INFO_FLAG:
                retVal = simInfoReport(srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.GPS_DATA_AND_LBS:
            case GidMsgID.GPS_REPORT_LBS:
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
                
            case GidMsgID.VEHICLE_SHARP_SLOWDOWN: // 车辆上报急减速事件数据
            case GidMsgID.VEHICLE_SHARP_ACCELERATE: // 车辆上报急加速事件数据
            case GidMsgID.VEHICLE_SHARP_TURE: // 车辆上报急转弯数据
            case GidMsgID.VEHICLE_SHARP_STOP: // 车辆上报急刹车事件数据
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
                
            case GidMsgID.QUERY_START_FLAG_REPLY:
                retVal = freqReport(srcBuf);
                break;
            case GidMsgID.TM_PARAM_UP_REQ:
                float displacement = Gid22Broker.getDisplacement(header.getDeviceId());
                int sleepInterval = Gid22Broker.getSleepIntervalAccOff(dataHandler); 
                Long distanceTotal = Gid22Broker.getDistanceTotal(header.getDeviceId());
                Long totalFuel = Gid22Broker.getTotalFuelConsumption(header.getDeviceId());
                sendBuf = GidInitParam.packRsp(header, displacement, sleepInterval, distanceTotal, totalFuel);
                break;
            case GidMsgID.TM_UTC_TIME_REQ:
                sendBuf = GidZeroTimeRsp.packRsp(header);
                break;
            case GidMsgID.GPS_UP_BATCH:
                retVal = gpsBigGpsReport(srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
            case GidMsgID.CAN_UP_BATCH:
                retVal = gpsBigCanReport(srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
                
                // 下面是2015.3.6新加的时间 >>>
            case GidMsgID.VEHICLE_EVENT_SHARP_SLOWDOWN: // 车辆上报急减速事件数据
            case GidMsgID.VEHICLE_EVENT_SHARP_ACCELERATE: // 车辆上报急加速事件数据
            case GidMsgID.VEHICLE_EVENT_SHARP_TURE: // 车辆上报急转弯数据
            case GidMsgID.VEHICLE_EVENT_SHARP_STOP: // 车辆上报急刹车事件数据
                retVal = vehicleEvent(header.msgId, srcBuf);
                // 发送通用应答
                sendBuf = GidUnRsp.packUnRsp(header);
                break;
                // <<< 2015.3.6
                
            // 下面是查询回复
            case GidMsgID.QUERY_CMD_REPLY:
                retVal = queryCmdReply(srcBuf);
                break;
            case GidMsgID.QUERY_SMS_NUM_REPLY:
                retVal = querySmsNumReply(srcBuf);
                break;
            case GidMsgID.QUERY_APN_REPLY:
                retVal = queryApnReply(srcBuf);
                break;
            case GidMsgID.QUERY_HEARTBEAT_INTERVAL_REPLY:
                retVal = queryHeartBeatIntervalReply(srcBuf);;
                break;
            case GidMsgID.QUERY_VEHICLE_TYPE_REPLY:
                retVal = queryVehicleTypeReply(srcBuf);
                break;
            case GidMsgID.QUERY_VEHICLE_OVER_SPEED_PARAM_RSP:
                retVal = queryVehicleOverSpeed(srcBuf);
                break;
            case GidMsgID.QUERY_TERMINAL_INFO_REPLY:
                retVal = versionReport(srcBuf);
                break;
            case GidMsgID.QUERY_SIM_INFO_REPLY:
                retVal = simInfoReport(srcBuf);
                break;
            // 设置回复
            case GidMsgID.TM_UN_RSP:
            case GidMsgID.SET_UPGRADE_SOFG_RSP:
                retVal = tmUnRsp(srcBuf);
                break;
            case GidMsgID.QUERY_STATEG_GW_REPLY:
            	retVal = stateGGWRsp(srcBuf);
            	break;
            case GidMsgID.QUERY_STATEG_REG_PARAM_REPLY: // 国网配置
            	retVal = stateGParamRsp(srcBuf);
            	break;
            	*/
            default:
                // 发送通用应答
                // 新协议增加了LBS终端上报基站大数据包, 最大的值变成 0xB0   
                if ((header.getMsgId() >= 0x0 && header.getMsgId() <= 0x4F)
                        || (header.getMsgId() >= 0x95 && header.getMsgId() <= 0xB0)) {
                    sendBuf = GidUnRsp.packUnRsp(header);
                }
                logger.warn("Not implemented MsgID: 0x{}", Integer.toHexString(header.getMsgId()));
                break;
        }

        if (sendBuf != null) {
            sendResponse(sendBuf);
        }

        return retVal;
    }

    /**
     * 解析数据包后确定返回内容后， 发送
     * @param sendBuf
     */
    public void sendResponse(ByteBuf sendBuf){
        if (channel != null) {
            NetUtil.writeAndFlush(channel, sendBuf);
        }
        //logger.warn("Please implement it.");
    }

    private int gpsReport(ByteBuf srcBuf, boolean isWarnPack){
        body = new GidGps(header.getBodyLen(), isWarnPack);
        int retVal = body.unpack(srcBuf);
        return retVal;
    }
    
    private int canReport(ByteBuf srcBuf){
        body = new GidCan(header.getBodyLen());
        int retVal = body.unpack(srcBuf);

        return retVal;
    }
//
//    private int versionReport(ChannelBuffer srcBuf){
//        body = new GidVersion(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        // 成功调用
//        if (retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        }
////        if (header.msgId == GidMsgID.QUERY_TERMINAL_INFO_REPLY && retVal == 0) {
//        // write if up version with 0x03
//        if (retVal == 0) {
//            Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        }
//            
//        return retVal;
//    }
//    
//    /** gps回传频率 */
//    private int freqReport(ChannelBuffer srcBuf){
//        body = new GidGPSFrequent();
//        int retVal = body.unpack(srcBuf);
//        // 成功调用
//        if (retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        }
//        if (retVal == 0) {
//            Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        }
//            
//        return retVal;
//    }
//
//    private int simInfoReport(ChannelBuffer srcBuf){
//        body = new GidSIMInfo(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        // 成功调用
//        if (retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        } else {
//            Gid22Broker.onSimInfo(dataHandler, header.getDeviceId(), (GidSIMInfo)body);
//        }
//        if (header.msgId == GidMsgID.QUERY_SIM_INFO_REPLY) {
//            Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        }
//            
//        return retVal;
//    }
//    
//
//    private int vehicleSharp(int msgId, ChannelBuffer srcBuf){
//        body = new GidSharp(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//            
//        return retVal;
//    }
//    
//    private int gpsBigGpsReport(ChannelBuffer srcBuf){
//        body = new GidBigGps(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        // 成功调用
//        if (retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        } else {
//            Gid22Broker.onBigGps(dataHandler, header.getDeviceId(), (GidBigGps)body);
//        }
//        return retVal;
//    }
//    
//    private int gpsBigCanReport(ChannelBuffer srcBuf){
//        body = new GidBigCan(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        // 成功调用
//        if (retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        } else {
//            Gid22Broker.onBigCan(dataHandler, header.getDeviceId(), (GidBigCan)body);
//        }
//        return retVal;
//    }
//    
//    /** 解析车辆事件数据 */
//    private int vehicleEvent(int msgId, ChannelBuffer srcBuf){
//        int retVal = 0;
//        body = new GidEvent(msgId, header.bodyLen);
//        retVal = body.unpack(srcBuf);
//        // 成功调用
//        if(retVal != 0) {
//            Gid22Broker.onHeartBeat(dataHandler, header.getDeviceId());
//        }else{
//            Gid22Broker.onVehicleEvent(dataHandler, header.getDeviceId(), (GidEvent)body);
//        }
//        return retVal;
//    }
//    
//    private int queryCmdReply(ChannelBuffer srcBuf) {
//        body = new GidGprsParam(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//
//    private int querySmsNumReply(ChannelBuffer srcBuf) {
//        body = new GidSmsNumReply(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    private int queryApnReply(ChannelBuffer srcBuf) {
//        body = new GidAsscii(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    } 
//    
//    private int queryHeartBeatIntervalReply(ChannelBuffer srcBuf) {
//        body = new GidHeartBeatInterval(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    private int queryVehicleTypeReply(ChannelBuffer srcBuf) {
//        body = new GidVehicleTypeReply(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    
//    private int queryVehicleOverSpeed(ChannelBuffer srcBuf) {
//        body = new GidOverSpeedParam(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    private int tmUnRsp(ChannelBuffer srcBuf) {
//        body = new GidUnRsp(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    private int stateGGWRsp(ChannelBuffer srcBuf) {
//        body = new GidStateGridGw(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
//    
//    private int stateGParamRsp(ChannelBuffer srcBuf) {
//        body = new GidStateGParam(header.bodyLen);
//        int retVal = body.unpack(srcBuf);
//        
//        Gid22Broker.onQueryCmdReply(dataHandler, header.getDeviceId(), this);
//        return retVal;
//    }
    
    /**
     * 校验， 最后一位是校验码
     * @param array
     * @param offset
     * @param length
     * @return true 表示校验正确
     */
    public static boolean checkSum(byte[] array, int offset, int length) {
        try {
            byte checkSUM = calcSum(array, offset, length-1);

            if(checkSUM == array[offset + length -1]) {
                return true;
            }
            logger.info("onGPSData check sum error! check sum is " + checkSUM + " check length " + length);
        } catch (Exception e) {
            logger.error("check sum parse error!");
        }
        return false;
    }
    
    /**
     * 计算校验和
     * @param src
     * @param srcIndex the first index of src
     * @param length the number bytes needed calc
     * @return 校验结果
     */
    public static byte calcSum(byte[] src, int srcIndex, int length) {
        byte checkSUM = 0;
        for (int i = 0; i < length; i++) {
            checkSUM = (byte) ((byte) checkSUM ^ src[i + srcIndex]);
        }
        return checkSUM;
    }
}
